/**
 * Simmah v0.0.5
 * 作者：快乐两小时
 * gitee：https://gitee.com/simmah
 * email：1164601554@qq.com
 */
layui.define(function (exports) {
    "use strict";

    // 初始化配置
    let config = {
        component: "/component",
        middleware: [],
        route: [],
        route_key: null
    }

    // 默认css样式
    const style_css = "*{box-sizing: border-box;}"

    //监听 哈希 事件
    window.addEventListener('hashchange', async function () {
        await jump()
    });

    /**
     * 中间件
     */
    let Middleware = function () {
        // 中间件组
        this.middlewares = []
    }

    /**
     * 使用中间件
     * @param {callback} callback 回调函数
     */
    Middleware.prototype.use = function (callback) {
        this.middlewares.push(callback)
    }

    /**
     * 验证中间件
     * @param {object} input 参数
     * @returns bool
     */
    Middleware.prototype.exec = async function (input) {
        for (const mw of this.middlewares) {
            if (mw(input) === false) {
                return false
            }
        }
        return true
    }

    // 创建中间件
    window.middleware = new Middleware()

    /**
     * 模板类
     */
    let Template = function () {
        this.v = "0.0.1"
    }

    /**
     * 规避html
     * @param {any} unsafe 变量
     * @returns any
     */
    Template.prototype.escape = function (unsafe) {
        if (typeof unsafe === "string") {
            return unsafe
                .replace(/&/g, "&amp;")
                .replace(/</g, "&lt;")
                .replace(/>/g, "&gt;")
                .replace(/"/g, "&quot;")
                .replace(/'/g, "&#039;");
        }
        return unsafe
    }

    /**
     * 模板渲染
     * @param {string} template 模板
     * @returns string
     */
    Template.prototype.render = function (template) {
        //if if-else else
        template = template.replace(/\{\#if\s+([^}]*)\}/g, '`;if($1) {tpl_str+=`')
        template = template.replace(/\{\#else\s+if\s+([^}]*)\}/g, '`;} else if($1) {tpl_str+=`')
        template = template.replace(/\{\#else\}/g, '`;} else {tpl_str+=`')
        template = template.replace(/\{\/if\}/g, '`;}tpl_str+=`')
        // for
        template = template.replace(/\{\#for\s+([^}]*)}/g, '`;for($1) {tpl_str+=`')
        template = template.replace(/\{\/for\}/g, '`;}tpl_str+=`')
        // each
        template = template.replace(/\{\#each\s+([^}]+)\s+as\s+([^}]+)}/g, '`;$1.forEach(function($2){tpl_str+=`')
        template = template.replace(/\{\/each\}/g, '`;});tpl_str+=`')
        // 安全变量
        template = template.replace(/\{\{(\w+(\.\w+)*)\}\}/g, "${escape($1)}")
        // Html内容插入
        template = template.replace(/\{\#html\s+(\w+(\.\w+)*)\}/g, "${$1}")
        // 整理
        /* let str = `let tpl_str = '';with(data){tpl_str+=\`${template}\`}return tpl_str;`
        try {
            const fn = new Function('data,escape', str)
            console.log(fn)
            return fn(data, this.escape)
        } catch (error) {
            throw new Error(error)
        } */
        let str = `onRender(function(){
        let tpl_str = '';
        tpl_str+=\`${template}\`
        return tpl_str;
        })`
        return str
    }

    /**
     * 获取当前url信息
     * @returns object
     */
    function url() {
        const fullHash = window.location.hash.substring(1)
        let targetHash;
        if (fullHash.indexOf('#') > -1) {
            targetHash = fullHash.substring(1, fullHash.indexOf('#', 1)) || "/";
        } else {
            targetHash = fullHash.substring(1) || "/";
        }
        let data = {
            query: Object.fromEntries((new URLSearchParams(window.location.search)).entries()),
            href: window.location.href.replace(window.location.origin, ""),
            hash: {
                href: targetHash
            }
        }
        if (!targetHash.startsWith("/")) {
            data.hash.href = "/" + targetHash
        }
        if (targetHash === "/") {
            data.hash.path = [""]
        } else {
            data.hash.path = targetHash.split("/")
        }
        return data
    }

    /**
     * 获取模板
     * @param {string} uri 
     * @param {number} timeout 
     * @returns 
     */
    async function getHtml(uri, timeout = 10000) {
        const controller = new AbortController();
        const id = setTimeout(() => controller.abort(), timeout);
        try {
            const response = await fetch(uri, { signal: controller.signal });
            clearTimeout(id);
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            const html = response.text()
                .then(html => new DOMParser().parseFromString(`<body>${html}</body>`, 'text/html'));
            return (await html).querySelector("body")
        } catch (error) {
            if (error.name === 'AbortError') {
                throw new Error('Request timed out.');
            } else {
                throw error;
            }
        }
    }

    /**
     * 匹配路由
     * @param {string} pattern 要匹配的路由
     * @param {string} path 当前路由
     * @returns object|false
     */
    function matchRoute(pattern, path) {
        // 将模式转换为正则表达式
        const regex = pattern.replace(/\/{(\w+)}/g, function (match, param) {
            return `/(?<${param}>\\w+)`;
        });

        // 创建正则表达式对象
        const routeRegex = new RegExp(`^${regex}$`);

        // 测试路径是否匹配模式
        const match = routeRegex.exec(path);

        if (!match) {
            return false;
        }

        // 如果匹配成功，返回捕获的参数
        const params = match.groups ? match.groups : {};

        // 特殊处理根路径
        if (pattern === '/' && path === '/') {
            return {};
        }

        // 返回捕获的参数或空对象
        return params;
    }

    /**
     * 跳转页面
     */
    async function jump() {
        const app = document.querySelector("#app")
        if (typeof app === "undefined") {
            return
        }
        const uri = url()
        let route_date = false

        let new_route
        for (const [key, val] of Object.entries(config.route)) {
            const is_route = matchRoute(val.path, uri.hash.href)
            if (is_route === false) {
                continue
            }
            route_date = is_route
            new_route = key
        }
        // 请求的参数
        const request = {
            route: route_date,
            query: uri.query
        }
        // 验证中间件
        const is_middleware = await window.middleware
            .exec({
                request,
                old_route: config.route[config.route_key] || null,
                new_route: config.route[new_route]
            })
        if (is_middleware) {

            if (config.route_key !== null) {
                config.route[config.route_key].dom.destroy()
                config.route[config.route_key].dom.tag.style.display = "none"
            }

            if (route_date === false) {
                console.error("404 not found")
            } else {
                config.route[new_route].dom.tag.style.display = "block"
                await config.route[new_route].dom.request(request)
                config.route_key = new_route
            }
        } else {
            history.back()
        }
    }

    /**
     * 路由类
     */
    class RouteRender {
        constructor(key) {
            this.key = key
            this.tag = document.createElement(`route-${config.route[key].name}`)
            this.tag.style.display = "none"
            this.shadow = this.tag.attachShadow({ mode: "open" })
            this.sheet = new CSSStyleSheet()
            this.tplRender = (new Template())
            this.sheet.replaceSync(style_css + config.route[key].tpl.querySelector("style").textContent)
            this.shadow.adoptedStyleSheets = [this.sheet]
            // 生命周期初始化
            this.onRequest = this.onRequest.bind(this)
            this.onMethods = this.onMethods.bind(this)
            this.onDestroy = this.onDestroy.bind(this)
            // 模板渲染
            this.onRender = this.onRender.bind(this)
            // 直接挂载
            this.mount()
            // 绑定到路由
            config.route[key]["dom"] = this
            document.querySelector("#app")?.appendChild(this.tag)
        }

        // 挂载
        async mount() {
            let script = config.route[this.key].tpl.querySelector("script").textContent
            let template = config.route[this.key].tpl.querySelector("template").innerHTML
            const fn = new Function(
                'dom',
                'onRequest',
                'escape',
                'onRender',
                'onMethods',
                'onDestroy', `
                function $state(obj){
                    return layui.app.proxy(obj,dom)
                }
                ${script}
                ${this.tplRender.render(template)}
                `)
            fn(this,
                this.onRequest,
                this.tplRender.escape,
                this.onRender,
                this.onMethods,
                this.onDestroy)
            this.shadow.innerHTML = await this.render()
            await this.methods()
        }

        /**
         * 执行请求
         */
        request = async function () { }

        /**
         * 调用请求
         * @param {callback} callback 回调函数
         */
        onRequest(callback) {
            this.request = callback
        }

        /**
         * 执行方法
         */
        methods = async function () { }

        /**
         * 调用方式
         * @param {callback} callback 回调函数
         */
        onMethods(callback) {
            this.methods = async () => {
                callback()
            }
        }

        /**
         * 执行卸载
         */
        destroy = async function () { }

        /**
         * 调用卸载
         * @param {callback} callback 
         */
        onDestroy(callback) {
            this.destroy = async () => {
                callback()
            }
        }

        /**
         * 执行渲染
         */
        render = async function () { }

        /**
         * 调用渲染
         * @param {callback} callback 回调函数
         */
        onRender(callback) {
            this.render = async () => {
                return callback()
            }
        }
    }

    /**
     * 组件
     * @param {string} tag_name 标签名
     * @param {string} uri 路径
     */
    async function Component(tag_name, uri) {
        try {
            const html = await getHtml(uri)
            customElements.define(tag_name, class extends HTMLElement {
                constructor() {
                    super()
                    // 标签名
                    this.name = this.tagName.toLowerCase()
                    this.sheet = new CSSStyleSheet()
                    this.tplRender = (new Template())
                    // 生命周期初始化
                    this.onProps = this.onProps.bind(this)
                    this.onMethods = this.onMethods.bind(this)
                    this.onDestroy = this.onDestroy.bind(this)
                    // 模板渲染
                    this.onRender = this.onRender.bind(this)
                }

                /**
                 * 连接
                 */
                async connectedCallback() {
                    this.tpl = html
                    this.sheet.replaceSync(style_css + this.tpl.querySelector("style").textContent)
                    let depth = Number(this.getAttribute("depth") || 1)
                    if (!depth) {
                        return
                    }
                    this.shadow = this.attachShadow({ mode: "open" })
                    this.shadow.adoptedStyleSheets = [this.sheet]
                    const observer = new MutationObserver(async (mutationList) => {
                        for (const mutation of mutationList) {
                            if (mutation.type === 'attributes') {
                                this.props()
                            }
                        }
                    })
                    observer.observe(this, { attributes: true });
                    await this.mount()
                }

                // 挂载
                async mount() {
                    let script = this.tpl.querySelector("script").textContent
                    let template = this.tpl.querySelector("template").innerHTML
                    const fn = new Function(
                        'dom',
                        'onProps',
                        'escape',
                        'onRender',
                        'onMethods',
                        'onDestroy', `
                        function $state(obj){
                            return layui.app.proxy(obj,dom)
                        }
                        ${script}
                        ${this.tplRender.render(template)}
                        `)
                    fn(this,
                        this.onProps,
                        this.tplRender.escape,
                        this.onRender,
                        this.onMethods,
                        this.onDestroy)
                    this.shadow.innerHTML = await this.render()
                    await this.props()
                    await this.methods()
                }

                // 元素从页面中移除
                disconnectedCallback() {
                    this.destroy()
                }

                /**
                 * 执行 道具
                 */
                props = async function () { }

                /**
                 * 调用 道具
                 * @param {callback} callback 回调函数
                 */
                onProps(callback) {
                    this.props = async () => {
                        callback()
                    }
                }

                /**
                 * 执行方法
                 */
                methods = async function () { }

                /**
                 * 调用方式
                 * @param {callback} callback 回调函数
                 */
                onMethods(callback) {
                    this.methods = async () => {
                        callback()
                    }
                }

                /**
                 * 执行卸载
                 */
                destroy = async function () { }

                /**
                 * 调用卸载
                 * @param {callback} callback 
                 */
                onDestroy(callback) {
                    this.destroy = async () => {
                        callback()
                    }
                }

                /**
                 * 执行渲染
                 */
                render = async function () { }

                /**
                 * 调用渲染
                 * @param {callback} callback 回调函数
                 */
                onRender(callback) {
                    this.render = async () => {
                        return callback()
                    }
                }
            })
        } catch (error) {
            // console.log(error)
        }
    }

    /**
     * 加载中间件
     * @returns Promise
     */
    const loadMiddleware = () => {
        const scriptPromises = [];

        for (const val of config.middleware) {
            const script = document.createElement('script');
            script.src = val;

            const scriptPromise = new Promise((resolve, reject) => {
                script.onload = resolve;
                script.onerror = reject;
            });

            document.head.appendChild(script);
            scriptPromises.push(scriptPromise);
        }

        return Promise.all(scriptPromises);
    };

    // simmah 实例
    let Simmah = function () {
        // 当前版本号
        this.v = "0.0.1"
    }

    /**
     * 初始化
     * @param {Object} options 配置
     */
    Simmah.prototype.init = async function (options) {

        config.component = options.component || config.component
        config.middleware = options.middleware || config.middleware
        config.route = options.route || config.route

        try {
            // 加载中间件 
            await loadMiddleware()
            // 加载路由组件
            for (const [key, value] of Object.entries(config.route)) {
                config.route[key]["tpl"] = await getHtml(value.page)
                new RouteRender(key)
            }
            // 首次跳转
            await jump()
            // 通知加载完毕
            const event = new Event("SimmahInit")
            document.dispatchEvent(event)
        } catch (error) {
            console.error(error);
        }
    }

    /**
     * 组件
     * @param {Array|String} comps 标签
     */
    Simmah.prototype.component = async function (comps, path = "") {
        if (typeof comps === "string") {
            comps = [comps]
        }
        try {
            for (const comp of comps) {
                if (path === "") {
                    path = `${config.component}/${comp}.html`
                }
                Component(comp, path)
            }
        } catch (error) {
            console.error(error)
        }
    }

    /**
     * 数据监听
     * @param {object} target 对象
     * @returns object
     */
    Simmah.prototype.proxy = function (target, dom) {
        return new Proxy(target, {
            get: (obj, prop) => {
                const value = Reflect.get(obj, prop);
                if (typeof value === 'object' && value !== null) {
                    return layui.app.proxy(value, dom); // 递归处理对象类型
                }
                return value;
            },
            set: (obj, prop, value) => {
                const oldValue = Reflect.get(obj, prop);
                const result = Reflect.set(obj, prop, value);

                if (!obj.hasOwnProperty(prop)) {
                    // 新增数据
                } else if (oldValue !== value) {
                    // 更新数据
                }
                // 刷新渲染
                dom.render().then((res) => {
                    dom.shadow.innerHTML = res
                    dom.methods()
                })
                if (typeof value === 'object' && value !== null) {
                    obj[prop] = this.$state(value); // 如果设置的新值是对象，则继续监听
                }

                return result;
            },
            deleteProperty: (obj, prop) => {
                // const oldValue = Reflect.get(obj, prop);
                const result = Reflect.deleteProperty(obj, prop);
                if (result) {
                    // 删除数据
                    // console.log(`Property ${prop} with value ${oldValue} has been deleted`);
                    // 刷新渲染
                    dom.render().then((res) => {
                        dom.shadow.innerHTML = res
                        dom.methods()
                    })
                }
                return result;
            }
        });
    }

    exports('app', new Simmah); // 输出模块
});  